package com.sojson.util.file.interfaces.impl;

import static com.sojson.util.file.FileUtil.LONG_FILE_SYSTEM_BUCKET_NAME_BASE;
import static com.sojson.util.file.FileUtil.THIS_LOCALITY_DIRECTORY_BUCKET_NAME_BASE;

import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.Date;

import org.apache.commons.io.IOUtils;

import com.aliyun.oss.OSS;
import com.aliyun.oss.OSSClient;
import com.aliyun.oss.OSSClientBuilder;
import com.aliyun.oss.model.OSSObject;
import com.aliyuncs.DefaultAcsClient;
import com.aliyuncs.auth.sts.AssumeRoleRequest;
import com.aliyuncs.auth.sts.AssumeRoleResponse;
import com.aliyuncs.auth.sts.AssumeRoleResponse.Credentials;
import com.aliyuncs.http.MethodType;
import com.aliyuncs.profile.DefaultProfile;
import com.aliyuncs.profile.IClientProfile;
import com.sojson.constant.Constant;
import com.sojson.constant.ConstantByMap;
import com.sojson.util.StringUtil;
import com.sojson.util.ali.AliUtil;
import com.sojson.util.ali.bean.AliConfig;
import com.sojson.util.file.FileUtil;
import com.sojson.util.file.interfaces.FileSystemInterface;
import com.sojson.util.id.GUIDUtil;

/**
 * OSS文件系统工具类
 * 
 * @author liu
 * @date 2020-09-02
 */
public class OssUtilImpl implements FileSystemInterface {

    private static OssUtilImpl ossUtilsImpl;
    private static final OSSClientBuilder OSS_CLIENT_BUILDER = new OSSClientBuilder();
    /** OSS的服务器的协议 */
    private static final String AGREEMENT = Constant.PROPERTIES.getProperty("so.oss.agreement");
    /** OSS的服务器地址 */
    private static final String IP_OSS = Constant.PROPERTIES.getProperty("so.oss.ip");
    /** STS的服务器地址 */
    private static final String IP_STS = Constant.PROPERTIES.getProperty("so.sts.ip");
    /** 获取阿里云用户的哪些权限 */
    private static final String POLICY = "{\n" + "    \"Version\": \"1\", \n" + "    \"Statement\": [\n" + "        {\n"
        + "            \"Action\": [\n" + "                \"oss:*\"\n" + "            ], \n"
        + "            \"Resource\": [\n" + "                \"acs:oss:*:*:*\" \n" + "            ], \n"
        + "            \"Effect\": \"Allow\"\n" + "        }\n" + "    ]\n" + "}";

    private OssUtilImpl() {}

    /**
     * 将本地文件上传到服务器
     * 
     * @param filePath 本地文件路径
     * @return 
     * @throws Exception
     */
    @Override
    public String upload(String filePath) throws IOException {
        return upload(LONG_FILE_SYSTEM_BUCKET_NAME_BASE, FileUtil.getFileName(filePath), filePath);
    }

    /**
     * 将本地文件上传到服务器
     * 
     * @param fileName 文件名
     * @param filePath 本地文件路径
     * @return 
     * @throws Exception
     */
    @Override
    public String uploading(String fileName, String filePath) throws IOException {
        return upload(LONG_FILE_SYSTEM_BUCKET_NAME_BASE, fileName, filePath);
    }

    /**
     * 将本地文件上传到服务器
     * 
     * @param fileSuffix 文件后缀
     * @param filePath 本地文件路径
     * @return 
     * @throws Exception
     */
    @Override
    public String uploadingBySuffix(String fileSuffix, String filePath) throws IOException {
        return upload(LONG_FILE_SYSTEM_BUCKET_NAME_BASE,
            new StringBuilder(GUIDUtil.generateGuid()).append(".").append(fileSuffix).toString(), filePath);
    }

    /**
     * 将本地文件上传到服务器
     * 
     * @param bucketName 服务器文件路径(一级目录)
     * @param filePath 本地文件路径
     * @return 
     * @throws Exception
     */
    @Override
    public String upload(String bucketName, String filePath) throws IOException {
        return upload(bucketName, FileUtil.getFileName(filePath), filePath);
    }

    /**
     * 将本地文件上传到服务器
     * 
     * @param bucketName 服务器文件路径(一级目录)
     * @param fileSuffix 文件后缀
     * @param filePath 本地文件路径
     * @return
     * @throws Exception
     */
    @Override
    public String uploading(String bucketName, String fileSuffix, String filePath) throws IOException {
        return upload(bucketName, new StringBuilder(GUIDUtil.generateGuid()).append(".").append(fileSuffix).toString(),
            filePath);
    }

    /**
     * 将本地文件上传到服务器
     * 
     * @param bucketName 服务器文件路径(一级目录)
     * @param fileName 文件名
     * @param filePath 本地文件路径
     * @return
     * @throws Exception
     */
    @Override
    public String upload(String bucketName, String fileName, String filePath) throws IOException {
        OSS ossClient = null;
        try {
            // 创建OSSClient实例
            ossClient = getConnection();
            ossClient.putObject(bucketName, fileName, new File(filePath));
        } finally {
            close(ossClient);
        }
        return fileName;
    }

    /**
     * 将文件流上传到服务器
     * 
     * @param bytes 文件的字节流
     * @param fileSuffix 文件后缀
     * @return 
     * @throws Exception
     */
    @Override
    public String uploading(byte[] bytes, String fileSuffix) throws IOException {
        return upload(bytes, getFileNameBySuffix(fileSuffix), LONG_FILE_SYSTEM_BUCKET_NAME_BASE);
    }

    /**
     * 将文件流上传到服务器
     * 
     * @param bytes 文件的字节流
     * @param fileName 文件名
     * @return 
     * @throws Exception
     */
    @Override
    public String upload(byte[] bytes, String fileName) throws IOException {
        return upload(bytes, fileName, LONG_FILE_SYSTEM_BUCKET_NAME_BASE);
    }

    /**
     * 将文件流上传到服务器
     * 
     * @param bytes 文件的字节流
     * @param fileSuffix 文件后缀
     * @param bucketName 服务器文件路径(一级目录)
     * @return
     * @throws Exception
     */
    @Override
    public String uploading(byte[] bytes, String fileSuffix, String bucketName) throws IOException {
        return uploading(new ByteArrayInputStream(bytes), fileSuffix, bucketName);
    }

    /**
     * 将文件流上传到服务器
     * 
     * @param bytes 文件的字节流
     * @param fileName 文件名
     * @param bucketName 服务器文件路径(一级目录)
     * @return
     * @throws Exception
     */
    @Override
    public String upload(byte[] bytes, String fileName, String bucketName) throws IOException {
        return upload(new ByteArrayInputStream(bytes), fileName, bucketName);
    }

    /**
     * 将文件流上传到服务器
     * 
     * @param inputStream 文件的输入流
     * @param fileSuffix 文件后缀
     * @return 
     * @throws Exception
     */
    @Override
    public String uploading(InputStream inputStream, String fileSuffix) throws IOException {
        return upload(inputStream, getFileNameBySuffix(fileSuffix), LONG_FILE_SYSTEM_BUCKET_NAME_BASE);
    }

    /**
     * 将文件流上传到服务器
     * 
     * @param inputStream 文件的输入流
     * @param fileName 文件名
     * @return 
     * @throws Exception
     */
    @Override
    public String upload(InputStream inputStream, String fileName) throws IOException {
        return upload(inputStream, fileName, LONG_FILE_SYSTEM_BUCKET_NAME_BASE);
    }

    /**
     * 将文件流上传到服务器
     * 
     * @param inputStream 文件的输入流
     * @param fileSuffix 文件后缀
     * @param bucketName 服务器文件路径(一级目录)
     * @return
     * @throws Exception
     */
    @Override
    public String uploading(InputStream inputStream, String fileSuffix, String bucketName) throws IOException {
        return upload(inputStream, getFileNameBySuffix(fileSuffix), bucketName);
    }

    /**
     * 将文件流上传到服务器
     * 
     * @param inputStream 文件的输入流
     * @param fileName 文件名
     * @param bucketName 服务器文件路径(一级目录)
     * @return
     * @throws Exception
     */
    @Override
    public String upload(InputStream inputStream, String fileName, String bucketName) throws IOException {
        OSS ossClient = null;
        try {
            // 创建OSSClient实例
            ossClient = getConnection();
            ossClient.putObject(bucketName, fileName, inputStream);
        } finally {
            close(ossClient);
            FileUtil.closeStream(inputStream);
        }
        return fileName;
    }

    /**
     * 获取文件名称
     * 
     * @param suffix
     * @return
     */
    private String getFileNameBySuffix(String suffix) {
        return new StringBuilder(GUIDUtil.generateGuid()).append(".").append(suffix).toString();
    }

    /**
     * 根据Key删除文件
     * 
     * @param key 密钥(上传后返回的字符串)
     * @return 成功还是失败(true:成功,false:失败)
     * @throws Exception
     */
    @Override
    public boolean remove(String key) throws IOException {
        return remove(LONG_FILE_SYSTEM_BUCKET_NAME_BASE, key);
    }

    /**
     * 根据Key删除文件
     * 
     * @param bucketName 服务器文件路径(一级目录)
     * @param key 密钥(上传后返回的字符串)
     * @return 成功还是失败(true:成功,false:失败)
     * @throws Exception
     */
    @Override
    public boolean remove(String bucketName, String key) throws IOException {
        OSS ossClient = null;
        try {
            ossClient = getConnection();
            ossClient.deleteObject(bucketName, key);
        } finally {
            close(ossClient);
        }
        return true;
    }

    /**
     * 从服务器下载文件到本地
     * 
     * @param key 密钥(上传后返回的字符串)
     * @return 
     * @throws Exception
     */
    @Override
    public boolean download(String key) throws IOException {
        return download(LONG_FILE_SYSTEM_BUCKET_NAME_BASE, key, THIS_LOCALITY_DIRECTORY_BUCKET_NAME_BASE);
    }

    /**
     * 从服务器下载文件到本地
     * 
     * @param bucketName 服务器文件路径(一级目录)
     * @param key 密钥(上传后返回的字符串)
     * @return 
     * @throws Exception
     */
    @Override
    public boolean downloading(String bucketName, String key) throws IOException {
        return download(bucketName, key, THIS_LOCALITY_DIRECTORY_BUCKET_NAME_BASE);
    }

    /**
     * 从服务器下载文件到本地
     * 
     * @param key 密钥(上传后返回的字符串)
     * @param targetPath 本地文件路径
     * @return 
     * @throws Exception
     */
    @Override
    public boolean download(String key, String targetPath) throws Exception {
        return download(LONG_FILE_SYSTEM_BUCKET_NAME_BASE, key, targetPath);
    }

    /**
     * 从服务器下载文件到本地
     * 
     * @param bucketName 服务器文件路径(一级目录)
     * @param key 密钥(上传后返回的字符串)
     * @param targetPath 本地文件路径
     * @return 
     * @throws Exception
     */
    @Override
    public boolean download(String bucketName, String key, String targetPath) throws IOException {
        OSS ossClient = null;
        OSSObject ossObject = null;
        OutputStream outputStream = null;
        try {
            // 创建OSSClient实例
            ossClient = getConnection();
            ossObject = ossClient.getObject(bucketName, key);
            FileUtil.makeDirectoryByPathname(targetPath);
            outputStream = new FileOutputStream(new StringBuilder(targetPath).append("/").append(key).toString());
            IOUtils.write(IOUtils.toByteArray(ossObject.getObjectContent()), outputStream);
        } finally {
            FileUtil.closeStream(outputStream);
            close(ossClient, ossObject);
        }
        return true;
    }

    /**
     * 根据Key获取文件流
     * 
     * @param key 密钥(上传后返回的字符串)
     * @return 文件字节流
     * @throws Exception
     */
    @Override
    public byte[] downloadGetBytes(String key) throws IOException {
        return downloadGetBytes(LONG_FILE_SYSTEM_BUCKET_NAME_BASE, key);
    }

    /**
     * 根据Key获取文件流
     * 
     * @param bucketName 服务器文件路径(一级目录)
     * @param key 密钥(上传后返回的字符串)
     * @return 文件字节流
     * @throws Exception
     */
    @Override
    public byte[] downloadGetBytes(String bucketName, String key) throws IOException {
        InputStream downloadGetInputStream = null;
        byte[] bytes = null;

        try {
            downloadGetInputStream = downloadGetInputStream(bucketName, key);
            bytes = IOUtils.toByteArray(downloadGetInputStream);
        } finally {
            FileUtil.closeStream(downloadGetInputStream);
        }

        return bytes;
    }

    /**
     * 根据Key获取文件流
     * 
     * @param key 密钥(上传后返回的字符串)
     * @return 
     * @throws Exception
     */
    @Override
    public InputStream downloadGetInputStream(String key) throws IOException {
        return downloadGetInputStream(LONG_FILE_SYSTEM_BUCKET_NAME_BASE, key);
    }

    /**
     * 根据Key获取文件流
     * 
     * @param bucketName 服务器文件路径(一级目录)
     * @param key 密钥(上传后返回的字符串)
     * @return 
     * @throws Exception
     */
    @Override
    public InputStream downloadGetInputStream(String bucketName, String key) throws IOException {
        OSS ossClient = null;
        OSSObject ossObject = null;
        InputStream objectContent = null;
        try {
            // 创建OSSClient实例
            ossClient = getConnection();
            ossObject = ossClient.getObject(bucketName, key);
            objectContent = ossObject.getObjectContent();
            ossObject.close();
        } finally {
            close(ossClient, ossObject);
        }
        return objectContent;
    }

    /**
     * 获取连接
     * 
     * @return
     * @throws IOException 
     */
    private OSS getConnection() throws IOException {
        AliConfig config = AliUtil.getConfig();
        return OSS_CLIENT_BUILDER.build(IP_OSS, config.getAccessKey(), config.getAccessSecret());
    }

    /**
     * 获取连接(别用!!!,私人资源生成Url最多只有12小时)
     * 
     * @return
     * @throws Exception 
     */
    @SuppressWarnings("unused")
    private OSS getConnectionByAccessSecurityToken() throws Exception {
        Credentials credentials = getAccessSecurityToken().getCredentials();

        // 用户拿到STS临时凭证后，通过其中的安全令牌（SecurityToken）和临时访问密钥（AccessKeyId和AccessKeySecret）生成OSSClient。
        // 创建OSSClient实例。注意，这里使用到了上一步生成的临时访问凭证（STS访问凭证）。
        return OSS_CLIENT_BUILDER.build(IP_OSS, credentials.getAccessKeyId(), credentials.getAccessKeySecret(),
            credentials.getSecurityToken());
    }

    /**
     * 关闭连接
     * 
     * @param ossClient
     */
    private void close(OSS ossClient) {
        // 关闭client
        if (StringUtil.isNotBlankObject(ossClient)) {
            ossClient.shutdown();
        }
    }

    /**
     * 关闭连接
     * 
     * @param ossClient
     * @throws IOException 
     */
    public void close(OSSObject ossObject) throws IOException {
        // 关闭client
        if (StringUtil.isNotBlankObject(ossObject)) {
            ossObject.close();
        }
    }

    /**
     * 关闭连接
     * 
     * @param ossClient
     * @throws IOException 
     */
    public void close(OSS ossClient, OSSObject ossObject) throws IOException {
        // 关闭client
        if (StringUtil.isNotBlankObject(ossObject)) {
            ossObject.close();
        }
        if (StringUtil.isNotBlankObject(ossClient)) {
            ossClient.shutdown();
        }
    }

    /**
     * 根据Key获取Url
     * 
     * @param key 密钥(上传后返回的字符串)
     * @return 得到的Url
     */
    @Override
    public String getUrl(String key) {
        return getUrl(LONG_FILE_SYSTEM_BUCKET_NAME_BASE, key);
    }

    /**
     * 根据Key获取Url
     * 
     * @param bucketName 服务器文件路径(一级目录)
     * @param key 密钥(上传后返回的字符串)
     * @return 得到的Url
     */
    @Override
    public String getUrl(String bucketName, String key) {
        OSSClient ossClient = null;

        try {
            StringBuilder append = new StringBuilder(AGREEMENT);
            append.append(bucketName);
            append.append(".");
            append.append(IP_OSS);
            append.append("/");
            append.append(key);

            return append.toString();
        } finally {
            close(ossClient);
        }
    }

    /**
     * 根据Key获取Url(临时路径)
     * 
     * @param key 密钥(上传后返回的字符串)
     * @return 得到的Url
     * @throws Exception 
     */
    @Override
    public String getUrlTemporary(String key) throws Exception {
        return getUrlTemporary(LONG_FILE_SYSTEM_BUCKET_NAME_BASE, key, ConstantByMap.FILE_URL_TIMEOUT);
    }

    /**
     * 根据Key获取Url(临时路径)
     * 
     * @param bucketName 服务器文件路径(一级目录)
     * @param key 密钥(上传后返回的字符串)
     * @param timeout 地址超时时间(单位: 毫秒)
     * @return 得到的Url
     * @throws Exception 
     */
    @Override
    public String getUrlTemporary(String key, long timeout) throws Exception {
        return getUrlTemporary(LONG_FILE_SYSTEM_BUCKET_NAME_BASE, key, timeout);
    }

    /**
     * 根据Key获取Url(临时路径)
     * 
     * @param bucketName 服务器文件路径(一级目录)
     * @param key 密钥(上传后返回的字符串)
     * @return 得到的Url
     * @throws Exception 
     */
    @Override
    public String getUrlTemporary(String bucketName, String key) throws Exception {
        return getUrlTemporary(bucketName, key, ConstantByMap.FILE_URL_TIMEOUT);
    }

    /**
     * 根据Key获取Url(临时路径)
     * 
     * @param bucketName 服务器文件路径(一级目录)
     * @param key 密钥(上传后返回的字符串)
     * @param timeout 地址超时时间(单位: 毫秒)
     * @return 得到的Url
     * @throws Exception 
     */
    @Override
    public String getUrlTemporary(String bucketName, String key, long timeout) throws Exception {
        OSS ossClient = null;
        try {
            ossClient = getConnection();

            // 生成以GET方法访问的签名URL，访客可以直接通过浏览器访问相关内容。
            return String.valueOf(
                ossClient.generatePresignedUrl(bucketName, key, new Date(System.currentTimeMillis() + timeout)));
        } finally {
            // 关闭OSSClient
            close(ossClient);
        }
    }

    /**
    * 获取临时访问凭证以及临时访问RAM角色的密钥信息(别用!!!,最多只能设置12小时)
    * @return
     * @throws Exception 
    */
    private AssumeRoleResponse getAccessSecurityToken() throws Exception {
        AliConfig config = AliUtil.getConfig();
        // 添加endpoint（直接使用STS endpoint，第一个参数留空，无需添加region ID）
        DefaultProfile.addEndpoint("", "Sts", IP_STS);
        // 构造default profile（参数留空，无需添加region ID）
        IClientProfile profile = DefaultProfile.getProfile("", config.getAccessKey(), config.getAccessSecret());
        // 用profile构造client
        DefaultAcsClient client = new DefaultAcsClient(profile);

        final AssumeRoleRequest request = new AssumeRoleRequest();
        request.setSysMethod(MethodType.POST);
        request.setRoleArn(config.getRole());
        request.setRoleSessionName(config.getSessionName());
        // 若policy为空，则用户将获得该角色下所有权限
        request.setPolicy(POLICY);
        // 设置凭证有效时间
        request.setDurationSeconds(60L * 15);

        return client.getAcsResponse(request);
    }

    public static OssUtilImpl getInstances() {
        if (StringUtil.isBlankObject(ossUtilsImpl)) {
            ossUtilsImpl = new OssUtilImpl();
        }
        return ossUtilsImpl;
    }

}